//
//  YuanXinTrackDataManager.m
//  YuanXinTrack
//
//  Created by GJS on 2018/5/10.
//  Copyright © 2018年 GJS. All rights reserved.
//

#import "YuanXinTrackDataManager.h"
#import "LFCGzipUtility.h"
#import "GJSUtilHTTPRequestClient.h"

#if DEBUG
#define ISDEBUG 0
#else
#define ISDEBUG 0
#endif

#define kLogStorageKey @"YuanXinLogStorageKey"
#define kLogCurrentUserIDKey @"YuanXinLogCurrentUserIDKey"
#define kTryAgainTimes 3
#define kLogFilesToBeDeletedKey @"YuanXinLogFilesToBeDeletedKey"

#define kSaveTrackLoginModelsDictPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"YuanXinLogLoginModelsDictPath"]

typedef NS_ENUM(NSUInteger, YuanXinTrackLogType) {
    YuanXinTrackLogTypeLogin = 0,
    YuanXinTrackLogTypeAction,
    YuanXinTrackLogTypeAny,
};

@interface YuanXinTrackDataManager ()

@property (nonatomic, strong) NSMutableDictionary *logDict;
@property (nonatomic, copy) NSString *storePath;
@property (nonatomic, strong) NSMutableDictionary *uploadingDict;

@end

@implementation YuanXinTrackDataManager

+ (instancetype)sharedManager {
    static YuanXinTrackDataManager *_manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _manager = [[YuanXinTrackDataManager alloc] init];
        _manager.numbersBeginUpload = 5; // 行为日志“满5条上传”
        NSString *currentUserID = [[NSUserDefaults standardUserDefaults] objectForKey:kLogCurrentUserIDKey];
        if (currentUserID) {
            _manager.currentUserID = currentUserID;
        }
    });
    return _manager;
}

- (void)dealloc {
    /*!
     -synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.

     -synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...
     - ...before reading in order to fetch updated values: remove the synchronize call
     - ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify
     - ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
     - ...for any other reason: remove the synchronize call
     */

    //[[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)setBaseAPI:(NSString *)baseAPI {
    if (baseAPI && _baseAPI != baseAPI) {
        _baseAPI = baseAPI;

        // 实例化的时候即app启动的时候(需在app启动时调用 [YuanXinTrackDataManager sharedManager]实例化)
        // 上传一次日志(准确说是在设置 baseAPI 的时候上传一次)
        [self uploadUserLog];
    }
}

- (void)saveLoginModelByUserCode:(NSString *)userCode trackLoginModel:(YuanXinTrackModel *)loginModel {
    if (userCode && loginModel) {
        NSDictionary *dict = @{userCode: loginModel};
        [NSKeyedArchiver archiveRootObject:dict toFile:kSaveTrackLoginModelsDictPath];
    }
}

- (YuanXinTrackModel *)getLoginModelByUserCode:(NSString *)userCode {
    NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithFile:kSaveTrackLoginModelsDictPath];;
    YuanXinTrackModel *loginModel = dict[userCode];
    return loginModel;
}

- (NSMutableDictionary *)logDict {
    if (!_logDict) {
        _logDict = [self getCachedLogDict];
    }

    return _logDict;
}

- (NSMutableDictionary *)uploadingDict {
    if (!_uploadingDict) {
        _uploadingDict = [NSMutableDictionary dictionary];
    }
    
    return _uploadingDict;
}

- (void)updateLogDict:(NSDictionary *)dict {
    if (dict) {
        _logDict = [dict mutableCopy];
        [[NSUserDefaults standardUserDefaults] setObject:dict forKey:kLogStorageKey];
    }
}

// 获取存储目录
- (NSString *)getStorageDir {
    NSString * _path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"YuanXinUserLog"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:_path]){
        [[NSFileManager defaultManager] createDirectoryAtPath:_path withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return _path;
}

- (NSString *)storePath {
    NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];
    NSTimeInterval a = [dat timeIntervalSince1970];
    NSString *timeString = [NSString stringWithFormat:@"%f", a];
    NSString *savePath = [[self getStorageDir] stringByAppendingFormat:@"/userLog%@.log",timeString];

    return savePath;
}

- (NSArray *)getAllLogType {
    NSMutableArray *mArr = [NSMutableArray array];
    [mArr addObject:@(YuanXinTrackLogTypeLogin)];
    [mArr addObject:@(YuanXinTrackLogTypeAction)];
    [mArr addObject:@(YuanXinTrackLogTypeAny)];

    return [mArr copy];
}

- (NSString *)getKeyFromLogType:(YuanXinTrackLogType)logType {
    NSString *key = nil;
    switch (logType) {
        case YuanXinTrackLogTypeLogin:
            key = @"loginLogs";
            break;
        case YuanXinTrackLogTypeAction:
            key = @"eventLogs";
            break;
        case YuanXinTrackLogTypeAny:
            key = @"anyLogs";
            break;

        default:
            break;
    }

    return key;
}

- (NSString *)getUploadApiFromLogType:(YuanXinTrackLogType)logType {
    NSString *api = nil;
    switch (logType) {
        case YuanXinTrackLogTypeLogin:
            //api = @"log/CollectUserLoginLogs";
            api = @"log/CollectLogs";
            break;
        case YuanXinTrackLogTypeAction:
            //api = @"log/CollectUserActiveLogs";
            api = @"log/CollectLogs";
            break;
        case YuanXinTrackLogTypeAny:
            api = @"log/CollectSeagullOperLog";
            break;

        default:
            break;
    }

    return api;
}

- (NSDictionary *)trackLoginDictWithModel:(YuanXinTrackModel *)model {
    NSDictionary *dict = @{
                           @"id": model.ID,
                           @"appID": self.appID,
                           @"userID": model.userID,
                           @"logLot": model.logLot ?: model.ID,
                           @"loginType": model.loginType,
                           @"loginTime": [model strFromDate:model.loginTime formatStr:nil],
                           @"deviceType": @"IOS",//model.systemName,
                           @"deviceID": model.uniqueID,
                           @"systemVersion": model.systemVersion,
                           @"deviceBrands": @"Apple",
                           @"deviceModel": [model.deviceModelName isEqualToString:@"Unknown"] ? model.deviceModel : model.deviceModelName,// 机型名称未知的话，直接返回机型代号
                           @"deviceLanguage": model.deviceLanguage,
                           @"timeZone": [model.timeZone localizedName:NSTimeZoneNameStyleShortStandard locale:[NSLocale currentLocale]] ?: [model.timeZone name],
                           @"appVersion": [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
                           };
    return dict;
}

- (NSDictionary *)trackActionDictWithModel:(YuanXinTrackModel *)model withLoginLog:(YuanXinTrackModel *)loginLogModel {
    /* 老版数据格式
    NSDictionary *dict = @{
                           @"appID": self.appID,
                           @"userID": model.userID,
                           @"logs": @[@{
                                          @"id": model.uuid,
                                          @"eventType": model.eventType,
                                          @"eventName": model.eventName,
                                          @"eventTime": [model strFromDate:model.eventTime formatStr:nil],
                                          @"eventData": model.eventData,
                                          }
                                      ],
                           };
    */

    // 行为日志存文件的格式示例(老版)：
    // @{
    //  @"eventLogs":
    //      @[
    //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
    //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
    //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
    //      ]
    //  }
    //
    // 行为日志存文件的格式示例(新版添加"loginLog",元数据放到"data")：
    // @{
    //  @"eventLogs":
    //      @[
    //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
    //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
    //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
    //      ]
    //  }

    NSDictionary *loginLogDict = [self trackLoginDictWithModel:loginLogModel] ?: @{};
    NSDictionary *dict = @{
                           @"appID": self.appID,
                           @"userID": model.userID,
                           @"logs": @[@{
                                          @"loginLog": loginLogDict,
                                          @"data": @{
                                                      @"id": model.ID,
                                                      @"logLot": model.logLot ?: loginLogModel.logLot,
                                                      @"eventType": model.eventType,
                                                      @"eventName": model.eventName,
                                                      @"eventTime": [model strFromDate:model.eventTime formatStr:nil],
                                                      @"eventData": model.eventData,
                                                     }
                                          }
                                      ],
                           };

    return dict;
}

- (NSDictionary *)trackBaseDict {
    YuanXinTrackModel *model = [YuanXinTrackModel new];
    NSDictionary *dict = @{
                           @"appID": self.appID,
                           @"deviceType": @"IOS",//model.systemName,
                           @"deviceID": model.uniqueID,
                           @"systemVersion": model.systemVersion,
                           @"deviceBrands": @"Apple",
                           @"deviceModel": [model.deviceModelName isEqualToString:@"Unknown"] ? model.deviceModel : model.deviceModelName,// 机型名称未知的话，直接返回机型代号
                           @"deviceLanguage": model.deviceLanguage,
                           @"appVersion": [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
                           };
    
    return dict;
}

- (NSDictionary *)trackAnyBaseDictWithModel:(YuanXinTrackModel *)model {    
    return model.eventData;
}

- (BOOL)recordTrackLog:(YuanXinTrackModel *)model {
    //TODO:todo->gjs,model转成字典
    //[self recordLogDict:[model dictionary]];
    if (!model.userID) {
        return NO;
    }
    YuanXinTrackModel *loginModel = [self getLoginModelByUserCode:model.userID];
    if (![model.userID isEqualToString:loginModel.userID]) {
        return NO;
    }

    // 行为日志“满5条上传”，附带上登录信息，如果5条行为日志跨天了，意思是与当前登录日志信息不在同一天的行为日志，“做个分割线”，新附加一个登录信息，该“伪造”的登录信息以分割线后面第一条行为日志的时间为登录时间
    NSDate *loginTime = loginModel.loginTime;
    NSDate *eventTime = model.eventTime;
    if ((![self isSameDay:loginTime date2:eventTime]) && ([eventTime timeIntervalSinceDate:loginTime] >= 0)) {
        loginModel.ID = [[[NSUUID UUID] UUIDString] lowercaseString]; // “伪造”的登录日志，id 也要“伪造”(生成新的id)
        loginModel.loginTime = eventTime;
        // 存(更新)登录信息
        [self saveLoginModelByUserCode:loginModel.userID trackLoginModel:loginModel];
        // 同时将该“伪造”的登录日志生成一条记录并上传
        NSDictionary *dict = [self trackLoginDictWithModel:loginModel];
        [self recordLogDict:dict type:YuanXinTrackLogTypeLogin];
    }

    NSDictionary *dict = [self trackActionDictWithModel:model withLoginLog:loginModel];
    return [self recordLogDict:dict type:YuanXinTrackLogTypeAction];
}

- (BOOL)recordAnyTrackLog:(YuanXinTrackModel *)model {
    NSDictionary *dict = [self trackAnyBaseDictWithModel:model];
    //sendUserId
    return [self recordLogDict:dict type:YuanXinTrackLogTypeAny];
}

- (BOOL)recordLoginLog:(YuanXinTrackModel *)model {
    //TODO:todo->gjs,model转成字典
    //[self recordLogDict:[model dictionary]];
    if (!model.userID) {
        return NO;
    }

    // 存登录信息
    [[NSUserDefaults standardUserDefaults] setObject:model.userID forKey:kLogCurrentUserIDKey];
    [self saveLoginModelByUserCode:model.userID trackLoginModel:model];

    NSDictionary *dict = [self trackLoginDictWithModel:model];
    return [self recordLogDict:dict type:YuanXinTrackLogTypeLogin];
}

// 更新 json/NSDictionary
- (BOOL)recordLogDict:(NSDictionary *)dict type:(YuanXinTrackLogType)logType {
    if (!dict) {
        return YES;
    }
    NSMutableDictionary *mLogDict = [self.logDict mutableCopy];
    //TODO:todo->gjs,更新
    NSString *key = [self getKeyFromLogType:logType];
    if (key) {
        switch (logType) {
                // 其他日志
            case YuanXinTrackLogTypeAny:
            {
                // 其他日志存文件的格式示例：
                // @{
                //  @"anyLogs":@{@"categoryKey":@{@"categoryValue":@[@{},...]}}
                // }
                NSMutableDictionary *mDict = mLogDict[key] ? [mLogDict[key] mutableCopy] : [NSMutableDictionary new]; // @{@"categoryKey":@{@"categoryValue":@[@{},...]}}
                NSString *categoryKey = dict[@"categoryKey"] ?: @"categoryKey"; // 拿到用来分类的字段
                NSMutableDictionary *mDictCategory = mDict[categoryKey] ? [mDict[categoryKey] mutableCopy] : [NSMutableDictionary new]; // @{@"categoryValue":@[@{},...]}
                NSString *categoryValue = dict[categoryKey] ?: @"categoryValue"; // 拿到分类的字段对应的值，作为key
                NSMutableArray *mArrCategory = mDictCategory[categoryValue] ? [mDictCategory[categoryValue] mutableCopy] : [NSMutableArray new]; // @[@{},...]
                [mArrCategory addObject:dict];
                [mDictCategory setObject:mArrCategory forKey:categoryValue];
                [mDict setObject:mDictCategory forKey:categoryKey];
                // 最后一步: 赋值
                [mLogDict setObject:mDict forKey:key];
            }
                break;
                // 登录日志
            case YuanXinTrackLogTypeLogin:
            {
                // 登录日志存文件的格式示例：@{@"loginLogs": @[@{},@{},@{}]}
                NSMutableArray *mArr = mLogDict[key] ? [mLogDict[key] mutableCopy] : [NSMutableArray new];
                [mArr addObject:dict];
                // 最后一步: 赋值
                [mLogDict setObject:mArr forKey:key];
            }
                break;
                // 行为日志
            case YuanXinTrackLogTypeAction:
            {
                // 行为日志存文件的格式示例(老版)：
                // @{
                //  @"eventLogs":
                //      @[
                //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
                //      ]
                //  }
                //
                // 行为日志存文件的格式示例(新版添加"loginLog",元数据放到"data")：
                // @{
                //  @"eventLogs":
                //      @[
                //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                //      ]
                //  }
                NSMutableArray *mArr = mLogDict[key] ? [mLogDict[key] mutableCopy] : [NSMutableArray new];
                BOOL foundTheUser = NO;
                for (NSInteger i=0; i<mArr.count; i++) { // 该数组存多个用户记录，每个用户记录是一个字典，其中"logs"存的是一个数组：对应用户产生的多个行为数据
                    NSMutableDictionary *tempDict = [mArr[i] mutableCopy]; // 每个用户记录过的数据
                    NSString *userID = tempDict[@"userID"];
                    if ([userID isEqualToString:dict[@"userID"]]) {
                        // 如果找到组织了(之前存过的该用户日志)，更新
                        NSMutableArray *mArrLogs = tempDict[@"logs"] ? [tempDict[@"logs"] mutableCopy] : [NSMutableArray new]; // 用户已经产生的多个行为数据
                        NSArray *logs = dict[@"logs"]; // 用户新产生的多个行为数据
                        // 拼接在一起
                        if ([logs isKindOfClass:[NSArray class]] && logs.count > 0) {
                            [mArrLogs addObjectsFromArray:logs];
                            [tempDict setObject:mArrLogs forKey:@"logs"];
                            [mArr replaceObjectAtIndex:i withObject:[tempDict copy]];
                        }

                        foundTheUser = YES;
                        break;
                    }
                }
                // 如果没找到组织(之前存过的该用户日志)，添加
                if (!foundTheUser) {
                    [mArr addObject:dict];
                }
                // 最后一步: 赋值
                [mLogDict setObject:mArr forKey:key];
            }
                break;

            default:
                break;
        }

        // 更新
        [self updateLogDict:mLogDict];
        if ([self checkIfShouldStoreByType:logType]) { // 检查缓存日志是否需要写成文件
            // storeCachedLogByType 不会将 self.logDict 全部写入文件，只是对应 logType 的部分，另外会更新 self.logDict
            NSArray *savePaths = [self storeCachedLogByType:logType withUserID:dict[@"userID"]]; // 存文件
            // 每次一批日志被写成文件后，上传
            for (NSString *savePath in savePaths) {
                [self uploadUserLogFile:savePath];
            }
        }
    } else {
        return NO;
    }
    return YES;
}

// 检查是否写成文件到沙盒
- (BOOL)checkIfShouldStoreByTotal {
    if (self.logDict) {
        NSData *data = [NSJSONSerialization dataWithJSONObject:self.logDict options:NSJSONWritingPrettyPrinted error:nil];
        if (!data) {
            return NO;
        }
        //TODO:todo->gjs,完善逻辑
        //if (data.length/1024 > 1000) { // 大于 1000kb(1M) 允许写入沙盒
        if (data.length/1024 > 100) { // 大于 100kb 允许写入沙盒
            return YES;
        }
    }
    return NO;
}

// 检查是否写成文件到沙盒
- (BOOL)checkIfShouldStoreByType:(YuanXinTrackLogType)logType {
    NSString *key = [self getKeyFromLogType:logType];
    if (self.logDict[key]) {
        switch (logType) {
            case YuanXinTrackLogTypeLogin:
            {
                return YES;
            }
                break;
            case YuanXinTrackLogTypeAction:
            {
                NSArray *arry = self.logDict[key];
                if (arry.count >= self.numbersBeginUpload) {
                    return YES;
                } else {
                    for (NSDictionary *dict in arry) {
                        NSArray *logs = [dict objectForKey:@"logs"];
                        if ([logs isKindOfClass:[NSArray class]] && [logs count] >= self.numbersBeginUpload) {
                            return YES;
                        }
                    }
                }
            }
                break;
            case YuanXinTrackLogTypeAny:
            {
                NSDictionary *dict = self.logDict[key];
                for (NSString *categoryKey in dict.allKeys) { // 拿到用来分类的字段
                    NSDictionary *dictCategory = dict[categoryKey]; // @{@"categoryValue":@[@{},...]}
                    for (NSString *categoryValue in dictCategory.allKeys) { // 拿到分类的字段对应的值，作为key
                        NSArray *logs = dictCategory[categoryValue]; // @[@{},...]
                        if ([logs isKindOfClass:[NSArray class]] && [logs count] >= self.numbersBeginUpload) {
                            return YES;
                        }
                    }
                }
            }
                break;

            default:
                break;
        }
    }
    return [self checkIfShouldStoreByTotal];
}

// 拿到缓存的 json/NSDictionary
- (NSMutableDictionary *)getCachedLogDict {
    NSDictionary *logDict = [[NSUserDefaults standardUserDefaults] objectForKey:kLogStorageKey];
    return [logDict mutableCopy] ?: [NSMutableDictionary dictionary];
}

// 拿到没有删除成功的文件
- (NSMutableDictionary *)getToBeDeletedFiles {
    NSDictionary *logFilesToBeDeleted = [[NSUserDefaults standardUserDefaults] objectForKey:kLogFilesToBeDeletedKey];
    return logFilesToBeDeleted ? [logFilesToBeDeleted mutableCopy] : [NSMutableDictionary dictionary];
}
// 更新待删除列表
- (void)updateToBeDeletedFiles:(NSDictionary *)files {
    if (files) {
        [[NSUserDefaults standardUserDefaults] setObject:files forKey:kLogFilesToBeDeletedKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

// 压缩 json/NSDictionary
- (NSData *)compressLogDict:(NSDictionary *)dict {
    if (dict) {
        NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
        if (data) {
            NSData *gzipData = [LFCGzipUtility gzipData:data];
            return gzipData;
        }
    }
    return nil;
}

// 将缓存数据存到沙盒里
- (void)storeCachedLog {
    // 分离下吧，不一块存了
    // 分离
    for (NSNumber *logTypeNumber in [self getAllLogType]) {
        YuanXinTrackLogType logType = [logTypeNumber integerValue];
        //[self storeCachedLogByType:logType withUserID:self.currentUserID]; // 行为日志仅存文件当前用户
        [self storeCachedLogByType:logType withUserID:nil]; // 行为日志存文件所有用户的数据，以供上传
    }
}

// 将缓存数据存到沙盒里
// userID 用于筛选行为日志里面对应用户产生的行为日志，可以传 nil，表示所有用户不筛选
- (NSArray<NSString *> *)storeCachedLogByType:(YuanXinTrackLogType)logType withUserID:(NSString *)userID {
    // 分离
    NSMutableDictionary *mLogDict = [self.logDict mutableCopy];
    NSString *key = [self getKeyFromLogType:logType];
    NSMutableArray<NSString *> *filePaths = [NSMutableArray new];
    if (mLogDict && mLogDict[key]) {
        switch (logType) {
                // 其他日志
            case YuanXinTrackLogTypeAny:
            {
                // 其他日志存文件的格式示例：
                // @{
                //  @"anyLogs":@{@"categoryKey":@{@"categoryValue":@[@{},...]}}
                // }
                NSMutableDictionary *mDict = [mLogDict[key] mutableCopy]; // @{@"categoryKey":@{@"categoryValue":@[@{},...]}}
                for (NSString *categoryKey in mDict.allKeys) { // 拿到用来分类的字段
                    NSMutableDictionary *mDictCategory = [mDict[categoryKey] mutableCopy]; // @{@"categoryValue":@[@{},...]}
                    for (NSString *categoryValue in mDictCategory.allKeys) { // 拿到分类的字段对应的值，作为key
                        NSMutableArray *mArrCategory = [mDictCategory[categoryValue] mutableCopy]; // @[@{},...]
                        NSMutableDictionary *mDictBase = [[self trackBaseDict] mutableCopy];
                        [mDictBase setObject:categoryValue forKey:categoryKey];
                        [mDictBase setObject:mArrCategory forKey:@"logs"];
                        // 按接口存文件的格式示例：
                        // @{
                        //  @"anyLogs":@{@"deviceID":@"",...,@"logs":@[@{},...]}
                        // }
                        NSString *filePath = [self storeLogWithDict:@{key: mDictBase}];
                        if (filePath) {
                            [filePaths addObject:filePath];
                            [mDictCategory removeObjectForKey:categoryValue];
                            [mDict setObject:mDictCategory forKey:categoryKey];
                        }
                    }
                }
                
                // 更新
                mLogDict[key] = mDict;
            }
                break;
                // 登录日志
            case YuanXinTrackLogTypeLogin:
            {
                // 登录日志存文件的格式示例：@{@"loginLogs": @[@{},@{},@{}]}
                NSString *filePath = [self storeLogWithDict:@{key: mLogDict[key]}];
                if (filePath) {
                    [filePaths addObject:filePath];
                    [mLogDict removeObjectForKey:key];
                }
            }
                break;
                // 行为日志
            case YuanXinTrackLogTypeAction:
            {
                // 行为日志存文件的格式示例(老版)：
                // @{
                //  @"eventLogs":
                //      @[
                //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{},@{},@{}]},
                //      ]
                //  }
                //
                // 行为日志存文件的格式示例(新版添加"loginLog",元数据放到"data")：
                // @{
                //  @"eventLogs":
                //      @[
                //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                //      ]
                //  }

                // 行为日志“满5条上传”，附带上登录信息，如果5条行为日志跨天了，意思是与当前登录日志信息不在同一天的行为日志，“做个分割线”，新附加一个登录信息，该“伪造”的登录信息以分割线后面第一条行为日志的时间为登录时间，所以需要遍历 logs 归下类(早知道当时应该用数据库存。。)

                NSMutableArray *mArray = [mLogDict[key] mutableCopy];
                // 逆序遍历
                for (NSDictionary *dict in mArray.reverseObjectEnumerator) { // 所有的不同用户
                    if (userID && ![userID isEqualToString:dict[@"userID"]]) { // 过滤用户
                        continue;
                    }
                    NSMutableDictionary *mDict = [dict mutableCopy];
                    NSArray *logs = dict[@"logs"];
                    NSMutableArray *mLogs = [NSMutableArray new];
                    NSString *loginTime = nil;
                    for (NSDictionary *logDict in logs) { // @{@"loginLog":@{},@"data":@{}}
                        [mLogs addObject:logDict];
                        
                        NSDictionary *loginLog = logDict[@"loginLog"];
                        if (loginLog && loginLog[@"loginTime"]) {
                            if (loginTime == nil) {
                                loginTime = loginLog[@"loginTime"];
                            }
                            // 如果下一个行为日志的登录日期不一样，存储之前的，重置后接着遍历
                            if (![loginTime isEqualToString:loginLog[@"loginTime"]]) {
                                [mDict setObject:mLogs forKey:@"logs"];
                                NSString *filePath = [self storeLogWithDict:@{key: mDict}];
                                if (filePath) {
                                    [mArray removeObject:dict];
                                    [filePaths addObject:filePath];
                                }
                                // 用完重置(清空)
                                [mLogs removeAllObjects];
                            }
                        }
                    }
                    
                    // 内层循环完之后检查 mLogs 是否还有数据
                    if (mLogs.count > 0) {
                        [mDict setObject:mLogs forKey:@"logs"];
                        NSString *filePath = [self storeLogWithDict:@{key: mDict}];
                        if (filePath) {
                            [mArray removeObject:dict];
                            [filePaths addObject:filePath];
                        }
                        // 用完重置(清空)
                        [mLogs removeAllObjects];
                    }
                }
                
                // 更新
                mLogDict[key] = mArray;
            }
                break;

            default:
                break;
        }
    }

    if (filePaths.count > 0) {
        // 写入文件后移除
        [self updateLogDict:mLogDict];
    }
    return filePaths;
}

#pragma mark -- Utils

// 压缩 json/NSDictionary 并存在沙盒里
- (NSString *)storeLogWithDict:(NSDictionary *)dict {
    if (!dict || dict.count == 0) {
        return nil;
    }
    NSData *data = [self compressLogDict:dict];
    NSString *filePath = self.storePath;
    if (data && [data writeToFile:filePath atomically:YES]) {
        return filePath;
    }
    return nil;
}

// 解压缩 log file
- (NSData *)uncompressLogFile:(NSString *)filePath {
    NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
    if (data) {
        NSData *ungzipData = [LFCGzipUtility ungzipData:data];
        return ungzipData;
    }
    return data;
}

// 解压缩 log file 并传成 json/NSDictionary
- (NSDictionary *)getStoredLogDictFromFile:(NSString *)filePath {
    NSData *data = [self uncompressLogFile:filePath];
    if (data) {
        NSDictionary *dictionary =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
        return dictionary;
    }
    return nil;
}

/**
 *  是否为同一天
 */
- (BOOL)isSameDay:(NSDate*)date1 date2:(NSDate*)date2
{
    NSCalendar* calendar = [NSCalendar currentCalendar];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
#pragma clang diagnostic pop
    if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0f) {
        unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
    }
    NSDateComponents* comp1 = [calendar components:unitFlags fromDate:date1];
    NSDateComponents* comp2 = [calendar components:unitFlags fromDate:date2];

    return [comp1 day]   == [comp2 day] &&
    [comp1 month] == [comp2 month] &&
    [comp1 year]  == [comp2 year];
}

+ (void)printFiles:(NSString *)path {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *strPath = path;

    NSDirectoryEnumerator<NSString *> * myDirectoryEnumerator;
    myDirectoryEnumerator = [fileManager enumeratorAtPath:strPath];

    while (strPath = [myDirectoryEnumerator nextObject]) {
        for (NSString * namePath in strPath.pathComponents) {
            NSLog(@"-----AAA-----%@", namePath);
        }
    }
}

+ (void)showFiles:(NSString *)path {
    // 1.判断文件还是目录
    NSFileManager * fileManger = [NSFileManager defaultManager];
    BOOL isDir = NO;
    BOOL isExist = [fileManger fileExistsAtPath:path isDirectory:&isDir];
    if (isExist) {
        // 2. 判断是不是目录
        if (isDir) {
            NSArray * dirArray = [fileManger contentsOfDirectoryAtPath:path error:nil];
            NSString * subPath = nil;
            for (NSString * str in dirArray) {
                subPath = [path stringByAppendingPathComponent:str];
                //BOOL isSubDir = NO;
                //[fileManger fileExistsAtPath:subPath isDirectory:&isSubDir];
                [self showFiles:subPath];
            }
        }else{
            NSLog(@"-----BBB-----%@",path);
        }
    }else{
        NSLog(@"你打印的是目录或者不存在");
    }
}

- (NSArray *)getAllLogFiles:(NSString *)dirPath;{
    //[[self class] printFiles:dirPath];
    //[[self class] showFiles:dirPath];
#if DEBUG
    [[self class] printFiles:dirPath];
#endif
    // 1.判断文件是否目录
    NSFileManager * fileManger = [NSFileManager defaultManager];
    BOOL isDir = NO;
    BOOL isExist = [fileManger fileExistsAtPath:dirPath isDirectory:&isDir];
    if (isExist) {
        // 2. 判断是不是目录
        if (isDir) {
            NSArray * dirArray = [fileManger contentsOfDirectoryAtPath:dirPath error:nil];
            NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:dirArray.count];
            NSString * subPath = nil;
            for (NSString * str in dirArray) {
                subPath  = [dirPath stringByAppendingPathComponent:str];
                BOOL isSubDir = NO;
                [fileManger fileExistsAtPath:subPath isDirectory:&isSubDir];
                if (!isSubDir) {
                    [mArr addObject:subPath];
                }
            }
            return [mArr copy];
        }else{
            return @[dirPath];
        }
    }else{
        return nil;
    }
}

// 删掉上次没删除成功的日志文件
- (void)deleteInvalidFiles {
    NSMutableDictionary *toBeDeletedFilesDict = [self getToBeDeletedFiles];
    NSArray *toBeDeletedFiles = [[toBeDeletedFilesDict allKeys] mutableCopy];
    for (NSString *toBeDeletedFile in toBeDeletedFiles) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:toBeDeletedFile]) {
            NSObject *object = [toBeDeletedFilesDict objectForKey:toBeDeletedFile];
            BOOL needDelete = YES;
            // 兼容旧数据
            if ([object isKindOfClass:[NSNumber class]]) {
                needDelete = [(NSNumber *)object boolValue];
            }            
            if ([object isKindOfClass:[NSString class]]) {
                needDelete = [(NSString *)object isEqualToString:@"uploaded"];
            }
            if (needDelete) {
                // 删掉文件
                NSError *fileError;
                BOOL deleted = [[NSFileManager defaultManager] removeItemAtPath:toBeDeletedFile error:&fileError];
                if (deleted && !fileError) {
                    [toBeDeletedFilesDict removeObjectForKey:toBeDeletedFile];
                }
            }
        } else {
            [toBeDeletedFilesDict removeObjectForKey:toBeDeletedFile];
        }
    }
    [self updateToBeDeletedFiles:toBeDeletedFilesDict];
}

- (BOOL)checkIfNeedUpload:(NSString *)filePath {
    if (!filePath) {
        return NO;
    }
    if (self.uploadingDict[filePath]) { // 正在上传中，不要重复上传
        return NO;
    }
    // 判断是否已上传过
    NSMutableDictionary *toBeDeletedFilesDict = [self getToBeDeletedFiles];
    if ([[toBeDeletedFilesDict allKeys] containsObject:filePath]) {
        // 如果是待删除的文件，不应该上传
        NSObject *object = [toBeDeletedFilesDict objectForKey:filePath];
        // 兼容旧数据
        if ([object isKindOfClass:[NSNumber class]] && [(NSNumber *)object boolValue]) {
            return NO;
        } else if ([object isKindOfClass:[NSString class]]) {
            if ([(NSString *)object isEqualToString:@"uploaded"]) {
                return NO;
            }
        }
    }
    return YES;
}

#pragma mark - 上传日志
// 上传日志
// 现在的逻辑改为“实时上传”：登录日志登录成功后就上传接口，行为日志满5条上传接口
// 这里只负责上传失败存在沙盒里的日志文件
- (void)uploadUserLog {
    // 在上传前“清池子”(存缓存)，虽然池子里面顶多不超过5条。。
    [self storeCachedLog]; // 上传之前先存下缓存
    // 删掉上次没删除成功的日志文件
    [self deleteInvalidFiles];
    // 上传
    if (self.baseAPI && [GJSUtilHTTPRequestClient shareInstance].baseURL) {
        //TODO:todo->gjs,上传日志
        NSArray *files = [self getAllLogFiles:[self getStorageDir]];
        if (!files) {
            return;
        }
        for (NSUInteger index=0; index<files.count; index++) {
            NSString *filePath = files[index];
            [self uploadUserLogFile:filePath];
        }
    }
}

/* 老版上传接口及参数
// 上传日志
- (void)uploadUserLogFile:(NSString *)filePath {
    NSMutableDictionary *toBeDeletedFilesDict = [self getToBeDeletedFiles];
    if ([[toBeDeletedFilesDict allKeys] containsObject:filePath] && [toBeDeletedFilesDict objectForKey:filePath]) { // 如果是待删除的文件，不应该上传
        return;
    }
    if (self.baseAPI && [GJSUtilHTTPRequestClient shareInstance].baseURL) {
        if (!filePath) {
            return;
        }

        NSDictionary *logDict = [self getStoredLogDictFromFile:filePath];
        if (logDict) {
            for (NSNumber *logTypeNumber in [self getAllLogType]) {
                YuanXinTrackLogType logType = [logTypeNumber integerValue];
                NSString *key = [self getKeyFromLogType:logType];
                NSString *uploadApi = [self getUploadApiFromLogType:logType];
                switch (logType) {
                        // 登录日志
                    case YuanXinTrackLogTypeLogin:
                    {
                        NSArray *parameters = logDict[key];
                        if (parameters) {
                            [self uploadUserLogTo:[self.baseAPI stringByAppendingPathComponent:uploadApi] from:filePath parameters:parameters type:logType tryAgainTimes:kTryAgainTimes];
                        }
                    }
                        break;
                        // 行为日志
                    case YuanXinTrackLogTypeAction:
                    {
                        NSArray *parameters = logDict[key];
                        if ([parameters isKindOfClass:[NSArray class]]) { // 兼容旧的数据存储结构
                            for (NSDictionary *paramDict in parameters) {
                                [self uploadUserLogTo:[self.baseAPI stringByAppendingPathComponent:uploadApi] from:filePath parameters:paramDict type:YuanXinTrackLogTypeAction tryAgainTimes:kTryAgainTimes];
                            }
                        } else if ([parameters isKindOfClass:[NSDictionary class]]) {
                            [self uploadUserLogTo:[self.baseAPI stringByAppendingPathComponent:uploadApi] from:filePath parameters:parameters type:YuanXinTrackLogTypeAction tryAgainTimes:kTryAgainTimes];
                        }
                    }
                        break;

                    default:
                        break;
                }
            }
        }
    }
}
*/

// 上传日志(新版上传接口及参数)
- (void)uploadUserLogFile:(NSString *)filePath {
    BOOL needUpload = [self checkIfNeedUpload:filePath];
    if (!needUpload) {
        return;
    }
    // 整理参数，上传
    if (self.baseAPI && [GJSUtilHTTPRequestClient shareInstance].baseURL) {
        NSDictionary *logDict = [self getStoredLogDictFromFile:filePath];
        if (logDict) {
            for (NSNumber *logTypeNumber in [self getAllLogType]) {
                YuanXinTrackLogType logType = [logTypeNumber integerValue];
                NSString *key = [self getKeyFromLogType:logType];
                NSString *uploadApi = [self getUploadApiFromLogType:logType];
                if (!uploadApi) {
                    continue;
                }
                switch (logType) {
                        // 其他日志
                    case YuanXinTrackLogTypeAny:
                    {
                        // 其他日志存文件的格式示例：
                        // @{
                        //  @"anyLogs":@{@"categoryKey":@{@"categoryValue":@[@{},...]}}
                        // }
                        // 按接口存文件的格式示例：
                        // @{
                        //  @"anyLogs":@{@"deviceID":@"",...,@"logs":@[@{},...]}
                        // }
                        NSDictionary *parameters = logDict[key];
                        if (parameters) {
                            // 日志“满5条上传”
                            NSMutableDictionary *mDictParameter = [parameters mutableCopy];
                            [self uploadUserLogTo:[self.baseAPI stringByAppendingPathComponent:uploadApi] from:filePath parameters:mDictParameter type:logType tryAgainTimes:kTryAgainTimes];
                        }
                    }
                        break;
                        // 登录日志
                    case YuanXinTrackLogTypeLogin:
                    {
                        // 登录日志存文件的格式示例：@{@"loginLogs": @[@{},@{},@{}]}
                        NSArray *parameters = logDict[key];
                        if (parameters) {
                            // 登录日志“实时上传”：意思是产生一条登录日志存一条，所以 parameters 现在只有一个对象
                            NSMutableDictionary *mDictParameter = [@{@"userLoginLog": [parameters firstObject] ?: @{}} mutableCopy];

                            [self uploadUserLogTo:[self.baseAPI stringByAppendingPathComponent:uploadApi] from:filePath parameters:mDictParameter type:logType tryAgainTimes:kTryAgainTimes];
                        }
                    }
                        break;
                        // 行为日志
                    case YuanXinTrackLogTypeAction:
                    {
                        // 行为日志存文件的格式示例(新版添加"loginLog",元数据放到"data")：
                        // @{
                        //  @"eventLogs":
                        //      @[
                        //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                        //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                        //          @{@"appID":"",@"userID":"",@"logs":@[@{@"loginLog":@{},@"data":@{}},...]},
                        //      ]
                        //  }

                        // 行为日志“满5条上传”，附带上登录信息，如果5条行为日志跨天了，意思是与当前登录日志信息不在同一天的行为日志，“做个分割线”，新附加一个登录信息，该“伪造”的登录信息以分割线后面第一条行为日志的时间为登录时间，存数据的时候已经处理好了，现在只需要拼接参数
                        NSDictionary *parameter = logDict[key];
                        if (parameter) {
                            //NSMutableDictionary *mDictParameter = [@{@"userActiveLogs": parameter ?: @{}} mutableCopy];
                            NSMutableDictionary *mDictParameter = [NSMutableDictionary new];
                            if ([parameter isKindOfClass:[NSDictionary class]]) {
                                NSArray *logs = parameter[@"logs"];
                                NSDictionary *firstLog = [logs firstObject];
                                if (firstLog) {
                                    NSDictionary *loginLog = firstLog[@"loginLog"];
                                    if (loginLog) {
                                        [mDictParameter setObject:loginLog forKey:@"userLoginLog"];

                                        NSMutableArray *mLogs = [NSMutableArray new];
                                        for (NSDictionary *actiongLog in logs) {
                                            NSDictionary *data = actiongLog[@"data"];
                                            if (data) {
                                                [mLogs addObject:data];
                                            }
                                        }
                                        [mDictParameter setObject:mLogs forKey:@"userActiveLogs"];
                                        [self uploadUserLogTo:[self.baseAPI stringByAppendingPathComponent:uploadApi] from:filePath parameters:mDictParameter type:logType tryAgainTimes:kTryAgainTimes];
                                    }
                                }
                            }
                        }
                    }
                        break;

                    default:
                        break;
                }
            }
        }
    }
}

// 上传日志
- (void)uploadUserLogTo:(NSString *)url from:(NSString *)filePath parameters:(id)parameters type:(YuanXinTrackLogType)logType tryAgainTimes:(NSInteger)tryAgainTimes {
    // 上传失败时会再尝试 tryAgainTimes 次
    // 比如上传失败时会再尝试3次，意思是接口返回失败时这个方法会再自己调用自己3次
    __weak typeof(self) weakSelf = self;
    // 存待删除列表，避免没删掉导致重复上传
    if (filePath) {
        // 标记正在上传中
        NSMutableDictionary *toBeDeletedFilesDict = [self getToBeDeletedFiles];
        [toBeDeletedFilesDict setObject:@"uploading" forKey:filePath];
        [self updateToBeDeletedFiles:toBeDeletedFilesDict];
        // 标记正在上传中
        [self.uploadingDict setObject:@"uploading" forKey:filePath];
    }
    // 成功后在回调里删掉本次上传的文件
    [[GJSUtilHTTPRequestClient shareInstance] POST:url parameters:parameters progress:nil success:^(NSURLSessionDataTask *task, id  _Nullable responseObject) {
        // 上传成功，先标记为“已上传”
        NSMutableDictionary *toBeDeletedFilesDict = [self getToBeDeletedFiles];
        [toBeDeletedFilesDict setObject:@"uploaded" forKey:filePath];
        [self updateToBeDeletedFiles:toBeDeletedFilesDict];
        // 移除“上传中”标记
        [self.uploadingDict removeObjectForKey:filePath];
        // 上传成功，删掉本地文件
        NSError *fileError;
        BOOL deleted = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&fileError];
        if (deleted && !fileError) {
            // 如果文件被成功删除，更新待删除列表
            [toBeDeletedFilesDict removeObjectForKey:filePath];
            [self updateToBeDeletedFiles:toBeDeletedFilesDict];
        }

        BOOL isSucess = NO;
        NSString *message = [NSString stringWithFormat:@"上传日志成功"];
        switch (logType) {
            case YuanXinTrackLogTypeAny:
            {
                if ([responseObject isKindOfClass:[NSDictionary class]]) {
                    isSucess = [[responseObject objectForKey:@"isSuccess"] boolValue];
                    message = [responseObject objectForKey:@"message"];
                    if (isSucess) {
                        message = @"其他日志上传成功";
                    } else {
                        message = @"其他日志上传成功,但是服务器返回说失败";
                    }
                } else {
                    message = @"其他日志上传成功,但是服务器返回结果数据格式不是json";
                }
            }
                break;
            case YuanXinTrackLogTypeLogin:
            {
                if ([responseObject isKindOfClass:[NSDictionary class]]) {
                    isSucess = [[responseObject objectForKey:@"isSuccess"] boolValue];
                    message = [responseObject objectForKey:@"message"];
                    if (isSucess) {
                        message = @"登录日志上传成功";
                    } else {
                        message = @"登录日志上传成功,但是服务器返回说失败";
                    }
                } else {
                    message = @"登录日志上传成功,但是服务器返回结果数据格式不是json";
                }
            }
                break;
            case YuanXinTrackLogTypeAction:
            {
                if ([responseObject isKindOfClass:[NSDictionary class]]) {
                    isSucess = [[responseObject objectForKey:@"isSuccess"] boolValue];
                    message = [responseObject objectForKey:@"message"];
                    if (isSucess) {
                        message = @"行为日志上传成功";
                    } else {
                        message = @"行为日志上传成功,但是服务器返回说失败";
                    }
                } else {
                    message = @"行为日志上传成功,但是服务器返回结果数据格式不是json";
                }
            }
                break;
                
            default:
                break;
        }
        // 打印上传日志结果
        NSLog(@"上传用户日志结果(%@):%@", message, responseObject);
#if ISDEBUG
        [weakSelf showMessage:message WithTitle:@"提示" cancelTitle:@"确定" otherTitles:nil];
#endif
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError *error) {

        // 失败时重新尝试，尝试3次，若都失败则返回错误信息
        if (tryAgainTimes > 0) {
            [weakSelf uploadUserLogTo:url from:filePath parameters:parameters type:logType tryAgainTimes:tryAgainTimes-1];
        } else {
            // 确定上传失败，移除“上传中”的标记
            [self.uploadingDict removeObjectForKey:filePath];
            NSString *message = @"上传用户日志失败";
            switch (logType) {
                case YuanXinTrackLogTypeAny:
                {
                    message = @"上传其他日志失败";
                }
                    break;
                case YuanXinTrackLogTypeLogin:
                {
                    message = @"上传登录日志失败";
                }
                    break;
                case YuanXinTrackLogTypeAction:
                {
                    message = @"上传行为日志失败";
                }
                    break;

                default:
                    break;
            }
            // 打印上传日志结果
            NSLog(@"上传用户日志失败(%@):%@", message, error.localizedDescription);
#if ISDEBUG
            [weakSelf showMessage:message WithTitle:@"提示" cancelTitle:@"确定" otherTitles:nil];
#endif
        }
    }];
}

- (UIAlertView *)showMessage:(NSString*)msg WithTitle:(NSString *)title cancelTitle:(NSString *)cancelTitle otherTitles:(nullable NSString *)otherButtonTitles, ... {

    UIAlertView *av = [[UIAlertView alloc] initWithTitle:title message:msg delegate:NULL cancelButtonTitle:nil otherButtonTitles:nil];
    // 获取可变参数的值
    if (![self isEmptyString:cancelTitle]) {
        [av addButtonWithTitle:cancelTitle];
    }
    NSString *str;
    va_list list;
    if(otherButtonTitles)
    {
        // 1.取得第一个参数的值
        [av addButtonWithTitle:otherButtonTitles];
        // 2.从第2个参数开始，依次取得所有参数的值
        va_start(list, otherButtonTitles);
        while ((str = va_arg(list, NSString *))){
            [av addButtonWithTitle:str];
        }
        va_end(list);
    }

    [av show];
    return av;
}

- (BOOL)isEmptyString:(NSString *)string {
    return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]==0?YES:NO;
    //return [string isEqualToString:@""];
}

@end
